package com.sdy.healthcode.biz.service.impl;

import com.sdy.common.model.BizException;
import com.sdy.common.utils.Assert;
import com.sdy.common.utils.DateUtil;
import com.sdy.common.utils.MapUtil;
import com.sdy.common.utils.StringUtil;
import com.sdy.healthcode.biz.constants.Constants;
import com.sdy.healthcode.biz.constants.RedisConstants;
import com.sdy.healthcode.biz.dto.schedule.ExpertsScheduleDailyDTO;
import com.sdy.healthcode.biz.dto.schedule.ScheduleSettings;
import com.sdy.healthcode.biz.model.ExpertsInformation;
import com.sdy.healthcode.biz.model.ExpertsScheduleDaily;
import com.sdy.healthcode.biz.mapper.ExpertsScheduleDailyMapper;
import com.sdy.healthcode.biz.model.ExpertsVisit;
import com.sdy.healthcode.biz.model.ExpertsVisitActual;
import com.sdy.healthcode.biz.model.HsDepartment;
import com.sdy.healthcode.biz.service.ExpertsInformationService;
import com.sdy.healthcode.biz.service.ExpertsScheduleDailyService;
import com.sdy.healthcode.biz.service.ExpertsVisitActualService;
import com.sdy.healthcode.biz.service.ExpertsVisitService;
import com.sdy.healthcode.biz.service.HolidayService;
import com.sdy.healthcode.biz.service.HsDepartmentService;
import com.sdy.mvc.service.impl.BaseServiceImpl;
import com.sdy.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author zzq
 * @since 2020-03-10
 */
@Slf4j
@Service
public class ExpertsScheduleDailyServiceImpl extends BaseServiceImpl<ExpertsScheduleDaily> implements ExpertsScheduleDailyService {
    @Autowired
    private ExpertsScheduleDailyMapper expertsScheduleDailyMapper;
    @Autowired
    private ExpertsInformationService expertsInformationService;
    @Autowired
    private ExpertsVisitService expertsVisitService;
    @Autowired
    private ExpertsVisitActualService expertsVisitActualService;
    @Autowired
    private HsDepartmentService hsDepartmentService;
    @Autowired
    private ExpertsScheduleDailyService expertsScheduleDailyService;
    @Autowired
    private RedisService redisService;
    @Autowired
    private HolidayService holidayService;

    @Override
    public void doDailySchedule(Date scheduleDate) {
        List<ExpertsScheduleDaily> expertsScheduleDailyList = expertsScheduleDailyMapper.queryToScheduleList(
                DateUtil.dateToMonth(scheduleDate) + "",
                Constants.WEEK_Z_STR[DateUtil.dateToWeek(scheduleDate)]);
        if (expertsScheduleDailyList.isEmpty()) {
            return;
        }
        Collection<ExpertsInformation> expertsInformationList = expertsInformationService.listByIds(
                expertsScheduleDailyList.stream().map(ExpertsScheduleDaily::getDoctorId).collect(Collectors.toSet())
        );
        Map<Integer, ExpertsInformation> doctorMap = MapUtil.collectionToMap(expertsInformationList, ExpertsInformation::getId);
        for (ExpertsScheduleDaily expertsScheduleDaily : expertsScheduleDailyList) {
            ExpertsInformation doctor = doctorMap.get(expertsScheduleDaily.getDoctorId());
            if (holidayService.checkScheduleStateForDay(scheduleDate, doctor == null ? null : doctor.getDepartmentId())) {
                expertsScheduleDaily.setCreateTime(new Date());
                expertsScheduleDaily.setScheduleDate(scheduleDate);
                save(expertsScheduleDaily);
            }
        }
    }

    @Override
    public void addScheduleOrFail(Integer doctorId, List<Integer> visitMonth, String visitDayStr, List<ScheduleSettings.ScheduleSettingsDoctorVisit> visits) {
        ExpertsInformation doctor = expertsInformationService.getById(doctorId);
        if (doctor == null || !Arrays.asList(1, 2, 4).contains(doctor.getSuspendService())) {
            return;
        }
        Date now = new Date();
        Date today = DateUtil.getDateBegin(now);
        Date date = redisService.get(RedisConstants.SCHEDULED_DATE, Date.class);
        Date effectDate;
        if (date != null) {
            effectDate = date;
        } else {
            effectDate = DateUtil.addDate(today, 8);
        }
        Date day = DateUtil.addDate(today, 1);
        while (!day.after(effectDate)) {
            if (visitMonth.contains(DateUtil.dateToMonth(day))
                    && visitDayStr.substring(0, 2).equals(Constants.WEEK_Z_STR[DateUtil.dateToWeek(day)])) {
                Integer existCnt = lambdaQuery()
                        .eq(ExpertsScheduleDaily::getDoctorId, doctorId)
                        .eq(ExpertsScheduleDaily::getScheduleDate, day)
                        .eq(ExpertsScheduleDaily::getVisitDay, visitDayStr)
                        .count();
                if (existCnt <= 0) {
                    if (holidayService.checkScheduleStateForDay(day, doctor.getDepartmentId())) {
                        for (ScheduleSettings.ScheduleSettingsDoctorVisit visit : visits) {
                            ExpertsScheduleDaily expertsScheduleDaily = new ExpertsScheduleDaily()
                                    .setScheduleDate(day)
                                    .setCreateTime(now)
                                    .setDoctorId(doctorId)
                                    .setVisitDay(visitDayStr)
                                    .setVisitNum(visit.getNum())
                                    .setVisitTime(visit.getTime());
                            save(expertsScheduleDaily);
                        }
                        log.info("补充新增医生排班[{}], [{}], [{}]", doctorId, visitDayStr,
                                visits.stream().map(ScheduleSettings.ScheduleSettingsDoctorVisit::getTime).collect(Collectors.joining(",")));
                    }
                }
            }
            day = DateUtil.addDate(day, 1);
        }
    }

    @Override
    public ScheduleSettings getScheduleSettings(Integer deptId) throws BizException {
        HsDepartment dept = hsDepartmentService.getById(deptId);
        Assert.isNull(dept, "诊室信息不存在。");
        // 获取医生信息
        List<ExpertsInformation> experts = expertsInformationService.lambdaQuery()
                .eq(ExpertsInformation::getDepartmentId, dept.getId()).list();
        List<ScheduleSettings.ScheduleSettingsDoctorDetail> expertsList = experts.stream().map(item -> new ScheduleSettings
                .ScheduleSettingsDoctorDetail()
                .setName(item.getName())
                .setAvatar(item.getPhotoPath())
                .setState(item.getSuspendService().equals(1) ? 1 : 2)
                .setDoctorId(item.getId()))
                .collect(Collectors.toList());
        // 获取已排班模板信息
        List<ExpertsScheduleDailyDTO> expertsScheduledList = expertsScheduleDailyMapper.queryScheduledTemplateList(deptId);
        ScheduleSettings scheduleSettings = new ScheduleSettings();
        scheduleSettings.setDoctors(MapUtil.collectionToMap(expertsList, ScheduleSettings.ScheduleSettingsDoctorDetail::getDoctorId));
        List<ScheduleSettings.ScheduleSettingsDay> settingsDays = new ArrayList<>();
        for (int i = 0; i < ScheduleSettings.WEEK_Z_STR_2.length; i++) {
            ScheduleSettings.ScheduleSettingsDay settingsDay = new ScheduleSettings.ScheduleSettingsDay();
            String weekStr = ScheduleSettings.WEEK_Z_STR_2[i];
            List<ScheduleSettings.ScheduleSettingsDayItem> settingDayItems = new ArrayList<>();
            // 当天模板信息
            List<ExpertsScheduleDailyDTO> expertsScheduleds = expertsScheduledList
                    .stream()
                    .filter(item -> item.getVisitDay().startsWith(weekStr))
                    .collect(Collectors.toList());
            for (int i1 = 0; i1 < ScheduleSettings.DAY_RANGE.length; i1++) {
                String day = ScheduleSettings.DAY_RANGE[i1];
                // 上/下午模板信息
                List<ExpertsScheduleDailyDTO> expertsScheduledMorning = expertsScheduleds
                        .stream()
                        .filter(item -> item.getVisitDay().endsWith(day))
                        .collect(Collectors.toList());
                ScheduleSettings.ScheduleSettingsDayItem dayItemMorning = new ScheduleSettings.ScheduleSettingsDayItem();
                dayItemMorning.setDoctors(new ArrayList<>());
                for (Map.Entry<Integer, ScheduleSettings.ScheduleSettingsDoctorDetail> entry : scheduleSettings.getDoctors().entrySet()) {
                    Integer doctorId = entry.getKey();
                    // 上/下午医生模板信息
                    List<ExpertsScheduleDailyDTO> expertsScheduledDoctor = expertsScheduledMorning
                            .stream()
                            .filter(item -> item.getDoctorId().equals(doctorId))
                            .collect(Collectors.toList());
                    List<ScheduleSettings.ScheduleSettingsDoctorVisit> visits = new ArrayList<>();
                    boolean doctorChecked = false;
                    for (String time : ScheduleSettings.TIME_RANGE[i1]) {
                        // 上/下午医生时间段模板信息
                        Optional<ExpertsScheduleDailyDTO> expertsScheduledDoctorTime = expertsScheduledDoctor
                                .stream()
                                .filter(item -> item.getVisitTime().equals(time))
                                .findAny();
                        visits.add(new ScheduleSettings.ScheduleSettingsDoctorVisit()
                                .setTime(time)
                                .setNum(expertsScheduledDoctorTime.isPresent()
                                        ? expertsScheduledDoctorTime.get().getVisitNum()
                                        : ScheduleSettings.DEFAULT_VISIT_NUM)
                                .setChecked(expertsScheduledDoctorTime.isPresent()));
                        doctorChecked = doctorChecked || expertsScheduledDoctorTime.isPresent();
                    }
                    List<Integer> months = new ArrayList<>();
                    boolean special = false;
                    if (!expertsScheduledDoctor.isEmpty()) {
                        if (StringUtil.isNotBlank(expertsScheduledDoctor.get(0).getCanVisitMonth())) {
                            special = true;
                            months = Arrays.stream(expertsScheduledDoctor.get(0).getCanVisitMonth().split(","))
                                    .filter(StringUtil::isNotBlank)
                                    .map(Integer::valueOf)
                                    .collect(Collectors.toList());
                        }
                    }
                    // 默认未排班医生勾选所有时间段
                    for (ScheduleSettings.ScheduleSettingsDoctorVisit item : visits) {
                        if (!doctorChecked) {
                            item.setChecked(true);
                        }
                    }
                    ScheduleSettings.ScheduleSettingsDoctor doctor = new ScheduleSettings.ScheduleSettingsDoctor()
                            .setDoctorId(doctorId)
                            .setSpecial(special)
                            .setMonths(months)
                            .setChecked(doctorChecked)
                            .setVisits(visits);
                    dayItemMorning.getDoctors().add(doctor);
                }
                settingDayItems.add(dayItemMorning);
            }
            settingsDay.setSettingDay(settingDayItems);
            settingsDays.add(settingsDay);
        }
        scheduleSettings.setSettings(settingsDays);
        return scheduleSettings;
    }

    @Override
    public void saveScheduleSettings(ScheduleSettings scheduleSettings) throws BizException {
        Assert.isTrue(scheduleSettings.getSettings() == null || scheduleSettings.getSettings().size() != ScheduleSettings.WEEK_Z_STR_2.length, 
                "排班时间错误");
        expertsScheduleDailyMapper.removeScheduleTemplateByDept(scheduleSettings.getDept());
        List<ScheduleSettings.ScheduleSettingsDay> settings = scheduleSettings.getSettings();
        for (int i = 0; i < settings.size(); i++) {
            ScheduleSettings.ScheduleSettingsDay settingsDay = settings.get(i);
            Assert.isTrue(settingsDay.getSettingDay() == null || settingsDay.getSettingDay().size() != ScheduleSettings.DAY_RANGE.length,
                    "一天排班上下午数据有误");
            String weekStr = ScheduleSettings.WEEK_Z_STR_2[i];
            List<ScheduleSettings.ScheduleSettingsDayItem> settingDay = settingsDay.getSettingDay();
            for (int i1 = 0; i1 < settingDay.size(); i1++) {
                String dayStr = ScheduleSettings.DAY_RANGE[i1];
                ScheduleSettings.ScheduleSettingsDayItem dayItem = settingDay.get(i1);
                Assert.isNull(dayItem.getDoctors(), "医生排班信息有误");
                for (ScheduleSettings.ScheduleSettingsDoctor doctor : dayItem.getDoctors()) {
                    if (doctor.getChecked()) {
                        Assert.isNull(doctor.getVisits(), "医生排班时间段信息有误");
                        ExpertsVisit expertsVisit = new ExpertsVisit();
                        expertsVisit.setDoctorId(doctor.getDoctorId())
                                .setCanVisitMonth(doctor.getSpecial()
                                        ? "," + doctor.getMonths().stream().map(Object::toString).collect(Collectors.joining(",")) + ","
                                        : "")
                                .setUpdateTime(new Date())
                                .setVisitDate(weekStr + dayStr);
                        expertsVisitService.save(expertsVisit);
                        List<ScheduleSettings.ScheduleSettingsDoctorVisit> checkedVisits = doctor.getVisits()
                                .stream()
                                .filter(ScheduleSettings.ScheduleSettingsDoctorVisit::getChecked)
                                .collect(Collectors.toList());
                        expertsScheduleDailyService.addScheduleOrFail(
                                doctor.getDoctorId(),
                                doctor.getSpecial() ? doctor.getMonths() : Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),
                                weekStr + dayStr,
                                checkedVisits);
                        for (ScheduleSettings.ScheduleSettingsDoctorVisit visit : checkedVisits) {
                            ExpertsVisitActual visitActual = new ExpertsVisitActual();
                            visitActual.setVisitId(expertsVisit.getId()).setVisitNum(visit.getNum()).setVisitTime(visit.getTime());
                            expertsVisitActualService.save(visitActual);
                        }
                    }
                }
            }
        }
    }

    @Override
    public Boolean isDoctorScheduled(Integer doctorId) {
        return lambdaQuery().eq(ExpertsScheduleDaily::getDoctorId, doctorId).count() > 0;
    }

    @Override
    public List<ExpertsScheduleDaily> listExpertsScheduleForDay(Date dateBegin, Date dateEnd, Integer deptId, Integer doctorId) {
        return expertsScheduleDailyMapper.listExpertsScheduleForDay(DateUtil.formatDate(dateBegin), DateUtil.formatDate(dateEnd), deptId, doctorId);
    }
}
